<?php
namespace Sfsn\Core\Schema;

class MysqlSchema extends Schema
{
/*
    public $typeMap = [
        'TINYINT' => 'int',
        'SMALLINT' => 'int',
        'MEDIUMINT' => 'int',
        'INT' => 'int',
        'BIGINT' => 'int',
        
        'FLOAT' => 'float',
        'DOUBLE' => 'double',
        'REAL' => 'real',
        'DECIMAL' => 'float',
        
        'DATE' => 'string',
        'DATETIME' => 'string',
        'TIMESTAMP' => 'string',
        'TIME' => 'string',
        'YEAR' => 'int',
        
        'CHAR' => 'string',
        'VARCHAR' => 'string',
        'BINARY' => 'string',
        'VARBINARY' => 'string',
        
        'TINYBLOB' => 'string',
        'BLOB' => 'string',
        'MEDIUMBLOB' => 'string',
        'LONGBLOB' => 'string',
        
        'TINYTEXT' => 'string',
        'TEXT' => 'string',
        'MEDIUMTEXT' => 'string',
        'LONGTEXT' => 'string',
        
        'ENUM' => 'string',
        'SET' => 'string'
    ];
    */

    
    public function tinyintFilter($column, $value)
    {
        //数据值
        $number = $this->integer($column, $value);
        if ($column->unsigned){
            //取值范围0，255
            $number = min($number,255);//取小于255
            $number = max($number,0);//区大于0
        }else{
            //取值范围-128，127
            $number = min($number,127);//取小于127
            $number = max($number,-128);//取大于-128
        }
        return $number;        
    }

    public function smallintFilter($column, $value)
    {
        //数据值
        $number = $this->integer($column, $value);
        if ($column->unsigned){
            //取值范围0，65535
            $number = min($number,65535);//取小于65535
            $number = max($number,0);//区大于0
        }else{
            //取值范围-32768，32767
            $number = min($number,32767);//取小于32767
            $number = max($number,-32768);//取大于-32768
        }
        return $number;
    }

    public function mediumintFilter($column, $value)
    {
        //数据值
        $number = $this->integer($column, $value);
        if ($column->unsigned){
            //取值范围0，16 777 215
            $number = min($number,16777215);//取小于16 777 215
            $number = max($number,0);//区大于0
        }else{
            //取值范围-8388608，8388607
            $number = min($number,8388607);//取小于8388607
            $number = max($number,-8388608);//取大于-8388608
        }
        return $number;
    }    
    
    public function intFilter($column, $value)
    {        
        //无符号整型32位操作系统时采用字符串返回
        if(PHP_INT_SIZE == 4 && $column->unsigned){
            if(is_numeric($value)){
                return (string) $value;
            }
        }
        return $this->integer($column, $value);      
        
    }

    public function bigintFilter($column, $value)
    {        
        //无符号整型64位操作系统时采用字符串返回
        if(PHP_INT_SIZE == 4 || $column->unsigned){
            if(is_numeric($value)){
                return (string) $value;
            }
        }
        return $this->integer($column, $value);
    }

    public function floatFilter($column, $value)
    {
        return $this->double($column, $value);
    }

    public function doubleFilter($column, $value)
    {
        return $this->double($column, $value);
    }

    public function realFilter($column, $value)
    {
        return $this->double($column, $value);
    }

    public function decimalFilter($column, $value)
    {
        return $this->double($column, $value);
    }

    public function numericFilter($column, $value)
    {
        return $this->double($column, $value);
    }

    public function dateFilter($column, $value)
    {
        return $value;
    }

    public function datetimeFilter($column, $value)
    {
        return $value;
    }

    public function timestampFilter($column, $value)
    {
        return $value;
    }

    public function timeFilter($column, $value)
    {
        return $value;
    }

    public function yearFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function charFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function varcharFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function binaryFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function varbinaryFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function tinyblobFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function blobFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function mediumblobFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function longblobFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function tinytextFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function textFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function mediumtextFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function longtextFilter($column, $value)
    {
        return $this->string($column, $value);
    }

    public function enumFilter($column, $value)
    {
        return $value;
    }

    public function setFilter($column, $value)
    {
        return $value;
    }

    /**
     * 获取转义后的内容
     * 
     * @param unknown $field            
     * @param unknown $value            
     */
    public function getValue($field, $value)
    {
        $column = $this->loadColumnSchema($field);
        $type = $column->type.'Filter';        
        return $this->$type($column,$value);
    }

    /**
     * 将抽象数据类型转换成合适的DBMS数据类型
     * 
     * @param unknown $type            
     * @return multitype:string |string|mixed|unknown
     */
    public function getColumnType($type)
    {
        // 映射表中已经有的，直接使用映射的类型
        if (isset($this->typeMap[$type])) {
            return $this->typeMap[$type];
            
            // 映射表中没有的类型，看看是不是形如 "Schema::TYPE_INT(11) DEFAULT 0" 之类的
        } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
            if (isset($this->typeMap[$matches[1]])) {
                return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
            }
            
            // 看看是不是形如 "Schema::TYPE_INT NOT NULL" 之类的，
            // 注意这一分支在第二分支之后
        } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
            if (isset($this->typeMap[$matches[1]])) {
                return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
            }
        }
        
        // 实在匹配不上映射表中的类型，那就原封不动返回吧
        return $type;
    }

    /**
     * 创建表字段类对象
     * 
     * @return \Sfsn\Core\Schema\ColumnSchema
     */
    protected function createColumnSchema()
    {
        return new ColumnSchema();
    }

    /**
     * $info数组 由 SQL 语句 "SHOW FULL COLUMNS FROM .
     * .." 而来，形式如下：
     * Field: id
     * Type: int(11)
     * Collation: NULL
     * Null: NO
     * Key: PRI
     * Default: NULL
     * Extra: auto_increment
     * Privileges: select,insert,update,references
     * Comment:
     */
    protected function loadColumnSchema($info)
    {
        $column = $this->createColumnSchema();
        
        // 字段名
        $column->name = $info['Field'];
        // 是否允许为NULL
        $column->allowNull = $info['Null'] === 'YES';
        // 是否是主键
        $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false;
        // 是否 auto_increment
        $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
        // 获取字段注释
        $column->comment = $info['Comment'];
        
        // 重点是这里，获取数据库字段类型，如上面的 int(11)
        $column->dbType = $info['Type'];
        // 是否是 unsigned
        $column->unsigned = stripos($column->dbType, 'unsigned') !== false;
        
        // 以下将把数据库类型，转换成对应的抽象类型，默认为 TYPE_STRING
        if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
            
            // 获取 int(11) 的 "int" 部分
            $type = strtolower($matches[1]);
            $column->type = $type;
            
            // 形如int(11) 的括号中的内容
            if (! empty($matches[2])) {
                // 枚举类型，还需要将所有枚举值写入 $column->enumValues
                if ($type === 'enum' || $type === 'set') {
                    $values = explode(',', $matches[2]);
                    foreach ($values as $i => $value) {
                        $values[$i] = trim($value, "'");
                    }
                    $column->enumValues = $values;
                    
                    // 如果不是枚举类型，那么括号中的内容就是精度了，如 decimal(19,4)
                } else {
                    $values = explode(',', $matches[2]);
                    $column->size = $column->precision = (int) $values[0];
                    if (isset($values[1])) {
                        $column->scale = (int) $values[1];
                    }
                    
                    // bit(1) 类型的，转换成 boolean
                    if ($column->size === 1 && $type === 'bit') {
                        $column->type = 'boolean';
                    } elseif ($type === 'bit') {
                        // 由于bit最多64位，如果超过 32 位，那么用一个 bigint 足以。
                        if ($column->size > 32) {
                            $column->type = 'bigint';
                            // 如果正好32位，那么用一个 interger 来表示。
                        } elseif ($column->size === 32) {
                            $column->type = 'integer';
                        }
                    }
                }
            }
        }
        
        // 获取PHP数据类型
        $column->phpType = $this->getColumnPhpType($column);
        
        // 处理默认值
        if (! $column->isPrimaryKey) {
            // timestamp 的话，要实际获取当前时间戳，而不能是字符串 'CURRENT_TIMESTAMP'
            if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP') {
                $column->defaultValue = time();
                
                // bit 的话，要截取对应的内容，并进行进制转换
            } elseif (isset($type) && $type === 'bit') {
                $column->defaultValue = bindec(trim($info['Default'], 'b\''));
                
                // 其余类型的，直接转换成PHP类型的值
            } else {
                $column->defaultValue = $column->phpTypecast($info['Default']);
            }
        }
        return $column;
    }

    protected function getColumnPhpType($column)
    {
        // 定义从抽象类型到PHP类型的映射
        static $typeMap = [
            'tinyint'=>'integer',
            'smallint' => 'integer',
            'integer' => 'integer',
            'bigint' => 'integer',
            'boolean' => 'boolean',
            'float' => 'double',
            'binary' => 'resource'
        ];
        
        // 除了上面的映射关系外，还有几个特殊情况：
        // 1. bigint字段，在64位环境下，且为singed时，使用integer来表示，否则string
        // 2. integer字段，在32位环境下，且为unsinged时，使用string表示，否则integer
        // 3. 映射中不存在的字段类型均使用string
        if (isset($typeMap[$column->type])) {
            if ($column->type === 'bigint') {
                return PHP_INT_SIZE == 8 && ! $column->unsigned ? 'integer' : 'string';
            } elseif ($column->type === 'integer') {
                return PHP_INT_SIZE == 4 && $column->unsigned ? 'string' : 'integer';
            } else {
                return $typeMap[$column->type];
            }
        } else {
            return 'string';
        }
    }
}